package com.rainyun.rc.command;

import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.Map.Entry;

import com.rainyun.rc.command.CmdFunc.*;
import com.rainyun.rc.util.RainStr;
import com.rainyun.rc.util.RainStr.StrEntry;

/** 指令节点 
 * @param <Sender> 执行者类型
 * */
public abstract class CmdPath<Sender> {

	/** 普通子节点列表 */
	Map<String, CmdPath<Sender>> subPaths = new LinkedHashMap<String, CmdPath<Sender>>();
	/** 值型子节点,如果不为null则忽略普通子节点 普通子节点和值型子节点不会一起处理! */
	CmdPath<Sender> subValuePath;
	/** 父节点 */
	CmdPath<Sender> parentPath;
	/** 名称 */
	String name;
	/** 原始名称 */
	String originName;
	/**可转类型(值型专属)*/
	Class<?> type = String.class;

	/**发送者检查不通过时,是否在信息和tab隐藏该节点 */
	boolean isTabWhenNotok;
	
	/**是否为值型节点*/
	boolean isValuePath;
	/**是否为列表值型节点*/
	boolean isListPath;
	
	/**是否为可选节点 */
	boolean isOptionalPath;
	/**可选节点默认值*/
	String defaultValue;

	/** 执行函数*/
	CmdExec<Sender> comExec;
	
	/** 补全函数集 */
	Map<String,TabExec<Sender>> tabExecMap = null;
	/** 验证函数集 */
	Map<String,CheckExec<Sender>> checkExecMap = null;
	
	/**指令附加属性,如权限等*/
	Map<String,Object> attrs = new HashMap<String, Object>();
	
	/**(常用)无参构造方法*/
	public CmdPath() {}

	/** (常用)添加子节点,子节点的父节点会被自动设置为此节点<br>
	 *  返回被添加的子节点对象
	 *  @param name 节点名称
	 *  <pre>
	 *  名称支持格式如
	 *  player 普通节点
	 *  &lt;player&gt; 必选值型节点
	 *  &lt;player...&gt; 列表必选值型节点
	 *  [player] 可选值型节点
	 *  [player]:Akron 可选值型节点的默认值
	 *  [player...] 列表可选值型节点  
	 *  处理后,最终的节点名称都是player
	 *  </pre>
	 *  */
	public CmdPath<Sender> add(String name){
		try {
			CmdPath<Sender> path = this.getClass().newInstance();
			path.setName(name);
			path.setParent(this);
			return path;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	
	/** (常用)获取子节点<br>
	 *  返回被子节点对象
	 *  @param name 节点名称(对于有值型子节点的节点,此项不重要)
	 *  */
	public CmdPath<Sender> get(String name){
		CmdPath<Sender> sub = subValuePath;
		if(sub==null) sub=getSubPaths().get(name);
		return sub;
	}
	
	/** (不常用)添加子节点,子节点的父节点会被自动设置为此节点<br>
	 *  返回当前节点对象
	 *  @see #add 推荐使用add()方法*/
	public CmdPath<Sender> addPath(CmdPath<Sender> path) {
		path.setParent(this);
		if(path.isValuePath)
			subValuePath = path;
		else
			subPaths.put(path.getName(), path);
		return this;
	}
	
	/** (指令传输入口)输入所有参数,然后执行>(执行者,参数(所有子节点))
	 * 执行者检查通过时返回true并执行指令业务*/
	public boolean input(Sender sd, String... args) {
		CmdArgs ca = new CmdArgs();
		ca.setArgs(args);
		return input(sd, args, ca);
	}
	/** 输入所有参数,然后执行>(执行者,参数列(会被逐步分割),传递集(input传递时用于记录相关数据))
	 * @throws ConvertException */
	boolean input(Sender sd, String[] args, CmdArgs ca){
		if (args.length == 0) {// 参数为空>说明已经传递到最后
			if(subValuePath != null&&subValuePath.isOptionalPath) {
				//值型子节点为可选,所以强行继续传递
				if(subValuePath.defaultValue!=null) {//设置默认值
					try {
						ca.set(subValuePath.getName(),this.convert(subValuePath.getType(),subValuePath.defaultValue,sd));
					}catch (ConvertException e){
						return true;
					}
				}
				return subValuePath.input(sd,new String[0],ca);
			}
			if(comExec!=null) {
				return checkAndExec(sd, ca);
			}else {//没有执行方法,执行帮助函数
				help(sd,"");
				return true;
			}
		}
		if (!globalCheck(sd,ca,true))
			return false;
		
		// _以下将传递参数给子节点
		// _无子节点,则认为是输入过多并进行提示,同时结束
		if (subValuePath == null && subPaths.isEmpty()) {
			help(sd,args[0]);
			return false;
		}

		if (subValuePath != null) {// 处理值型子节点
			if(!subValuePath.isListPath) {
				// 加入传递值
				try {
					ca.set(subValuePath.getName(),this.convert(subValuePath.getType(),args[0],sd));
				}catch (ConvertException e){
					return true;
				}
				args = Arrays.copyOfRange(args, 1, args.length);// 去除首位
				return subValuePath.input(sd, args, ca);
			}else {//列表值型节点
				Object objarr = Array.newInstance(subValuePath.getType(),args.length);
				for(int i=0;i<args.length;i++) {
					try {
						Object obj = this.convert(subValuePath.getType(),args[i],sd);
						Array.set(objarr,i,obj);
					}catch (ConvertException e){
						return true;
					}
				}
				ca.set(subValuePath.getName(),objarr);
				return subValuePath.input(sd,new String[0], ca);//让他直接执行
			}
		} else
		for (Entry<String, CmdPath<Sender>> en : subPaths.entrySet()) {// 筛选符合要求的子节点,传递
			if (args[0].equals(en.getKey())) {
				args = Arrays.copyOfRange(args, 1, args.length);
				// 去除首位
				CmdPath<Sender> path = en.getValue();
				return path.input(sd, args, ca);
			}	
		}
		//匹配失败,可能是输错了
		help(sd,args[0]);
		return false;
	}
	/** (入口)补全指令>执行者,需要补全的子节点 */
	public List<String> tabInput(Sender sd, String[] args) {
		CmdArgs ca = new CmdArgs();
		ca.setArgs(args);
		return tabInput(sd,args,ca);
	}
	
	/** 判断权限,补全指令>执行者,需要补全的子节点,传递参数集 
	 * @throws ConvertException */
	List<String> tabInput(Sender sd, String[] args,CmdArgs ca) {
		
		if(args.length>1) {
			String[] args0 = Arrays.copyOfRange(args, 1, args.length);
			// 去除首位
			if (subValuePath != null) {// 处理值型子节点
				if(!subValuePath.isListPath) {//普通值型节点
					try{
						ca.set(subValuePath.getName(),this.convert(subValuePath.getType(),args[0],sd));// 加入传递值
					}catch (ConvertException e){
						return new ArrayList<>();
					}
					if (isTabWhenNotok()||subValuePath.globalCheck(sd,ca,false))
					return subValuePath.tabInput(sd, args0,ca);
				}else {//列表值型子节点
					Object objarr = Array.newInstance(subValuePath.getType(),args.length);
					for(int i=0;i<args.length;i++) {
						try{
							Object obj = this.convert(subValuePath.getType(),args[i],sd);
							Array.set(objarr,i,obj);
						}catch (ConvertException e){
							return new ArrayList<>();
						}
					}
					ca.set(subValuePath.getName(),objarr);
					//传递列表
					if (isTabWhenNotok()||subValuePath.globalCheck(sd,ca,false))
						return tabInput(sd,new String[]{args[args.length-1]},ca);
						//传递最后一位用于补全
				}
			}else {
				CmdPath<Sender> sub = subPaths.get(args[0]);
				if (sub != null) {
					if (isTabWhenNotok()||sub.globalCheck(sd,ca,false))
						return sub.tabInput(sd, args0,ca);
				}
			}
		}else {
			String fix = args.length==1?args[0]:null;//要补全的字符串
			if (subValuePath != null) {// 处理值型节点
				if (isTabWhenNotok()||subValuePath.globalCheck(sd, ca,false))
					return subValuePath.complete(sd,fix,ca);
			} else {//普通节点补全
				List<String> list = new ArrayList<String>();
				for (String name : subPaths.keySet()) 
				if (fix==null||name.startsWith(fix))//识别开头
				if (isTabWhenNotok()||subPaths.get(name).globalCheck(sd,ca,false))
					list.add(name);
				return list;
			}
		}
		
		return new ArrayList<String>();
	}
	
	/** 检查并执行指令,若执行者不通过检查,返回false,通过调用执行函数>执行者,传递值 */
	public boolean checkAndExec(Sender sd, CmdArgs ca) {
		if (globalCheck(sd, ca,true)&&check(sd, ca)) {
			excute(sd,ca,this.checkAttr("async",true));
			return true;
		}else return false;
	}

	/**执行命令函数>执行者,传递值,是否异步*/
	public void excute(Sender sd, CmdArgs ca,boolean async) {
		if(async){
			new Thread(()->{
				try {
					comExec.excute(sd,ca);
				}catch (Throwable e){
					handleExecError(sd,e);
				}
			}).start();
		}else synchronized (Thread.currentThread()){
			try {
				comExec.excute(sd,ca);
			}catch (Throwable e){
				handleExecError(sd,e);
			}
		}
	}

	/**执行异常处理器*/
	public void handleExecError(Sender sd,Throwable e) {
		e.printStackTrace();
	}
	
	/** (全局校验器)检查执行者是否符合条件>(执行者,参数集,是否在执行前)<br>
	 *  默认检查通过(返回true)
	 * 	该方法在参数传递,执行等需要进行条件判断的地方都会执行<br>
	 * (另外,参数集的内容是不稳定的,注意判断)<br>
	 * (建议重写该方法)*/
	public abstract boolean globalCheck(Sender sd, CmdArgs ca,boolean ready);
	
	/** 执行帮助函数,用于提示执行者>(执行者,有误字符串)<br>
	 * 函数会在执行者输入不全或有误时,或者该节点没有执行函数时执行
	 * (建议重写该方法)*/
	public abstract void help(Sender sd,String arg);
	
	/** 补全指令>(执行者,需要补全的部分,参数集) */
	List<String> complete(Sender sd, String arg,CmdArgs ca) {
		List<String> sl = null;
		Map<String, TabExec<Sender>> tm = getTabExecMap();
		if(tm!=null&&attrs.containsKey("tab")) {
			TabExec<Sender> exec = tm.get(attrs.get("tab"));
			if(exec!=null)sl = exec.excute(sd,this.name,ca);
		}
		// 获取数据源
		if (arg == null)
			return sl;
		if (sl == null)
			return null;
		List<String> list = new ArrayList<String>();
		for (String str : sl) {// 筛选
			if (str.toLowerCase().startsWith(arg.toLowerCase())) {
				list.add(str);
			}
		}
		return list;
	}
	/**校验参数,如果找不到校验函数,则认为不需要校验,返回true*/
	public boolean check(Sender sd,CmdArgs ca) {
		Map<String, CheckExec<Sender>> vm = getCheckExecMap();
		if(vm!=null&&attrs.containsKey("check")) {
			CheckExec<Sender> exec = vm.get(attrs.get("check"));
			return exec!=null?exec.check(sd, ca):true;
		}
		return true;
	}

	/** 获取子节点 */
	public Map<String, CmdPath<Sender>> getSubPaths() {
		return subPaths;
	}
	/** 获取父节点 */
	public CmdPath<Sender> getParent() {
		return parentPath;
	}
	/** 设置父节点 */
	public void setParent(CmdPath<Sender> supPath) {
		this.parentPath = supPath;
		if (parentPath != null)
		if(isValuePath)
			parentPath.subValuePath = this;
		else
			parentPath.getSubPaths().put(getName(), this);
	}
	/** 获取名称 */
	public String getName() {
		return name;
	}
	/**设置名称,(自动识别<>,[],...,[]:default 语法)*/
	public CmdPath<Sender> setName(String name) {
		String real_name = RainStr.inner(name,"<",">");
		if(real_name!=null) {
			isValuePath = true;
		}else{
			StrEntry en = RainStr.toEntry(name,":");
			if(en!=null) {
				name = en.getKey();
				defaultValue = en.getValue();
			}
			real_name = RainStr.inner(name,"[","]");
			if(real_name!=null) {
				isValuePath = true;
				isOptionalPath = true;
			}
		}
		if(isValuePath) {
			String list_name = RainStr.end(real_name,"...");
			if(list_name!=null) {
				isListPath = true;
				real_name = list_name;
			}
		}
		
		this.name = real_name!=null?real_name:name;
		this.originName = name;
		return this;
	}
	/**获取执行函数*/
	public CmdExec<Sender> getComExec() {
		return comExec;
	}
	/**设置执行函数(具体的指令代码都在这)
	 * <pre>
	 * (Sender 执行者,ComArgs 参数集)->{
	 * 	执行代码...
	 * 	return 布尔值;	//代表执行是否成功(作用不大)
	 * }
	 * </pre>
	 * */
	public CmdPath<Sender> setComExec(CmdExec<Sender> comfun) {
		this.comExec = comfun;
		return this;
	}
	
	/**发送者检查不通过时,是否依然在tab键提示节点  */
	public boolean isTabWhenNotok() {
		return isTabWhenNotok;
	}
	/**设置发送者检查不通过时,是否依然在tab键提示节点 */
	public CmdPath<Sender> setTabWhenNotok(boolean isTabWhenNotok) {
		this.isTabWhenNotok = isTabWhenNotok;
		return this;
	}
	
	/**设置节点属性值*/
	public CmdPath<Sender> setAttr(String name,Object value) {
		attrs.put(name, value);
		return this;
	}
	/**获取节点属性值 */
	public <T> T getAttr(Object name) {
		return (T) attrs.get(name);
	}
	/**获取父节点属性,若父节点无此属性,将逐步往上搜寻 */
	public <T> T getParentAttr(String name) {
		if(parentPath!=null){
			Object pv = parentPath.getAttr(name);
			if(pv==null) {
				return parentPath.getParentAttr(name);
			}else return (T) pv;
		}else
			return null;
	}
	/**节点是否具有属性*/
	public boolean hasAttr(String name) {
		return attrs.get(name)!=null;
	}
	/**是否节点属性值为参数checkvalue*/
	public boolean checkAttr(String name,Object checkvalue) {
		return checkvalue.equals(attrs.get(name));
	}
	/**获取节点属性值,若无属性则返回默认值def*/
	public Object getAttrOrDef(Object name,Object def) {
		Object v = getAttr(name);
		return v!=null?v:def;
	}
	
	/** 获取值型子节点 */
	public CmdPath<Sender> getSubValuePath() {
		return subValuePath;
	}
	/** 设置值型子节点 */

	public void setSubValuePath(CmdPath<Sender> subValuePath) {
		this.subValuePath = subValuePath;
	}
	
	/**字符串转换为具体类,无法转换则返回null,转换错误则抛出转换错误异常*/
	public <T> T convert(Class<T> clazz,String str,Sender sd) throws ConvertException{
		if(clazz.equals(String.class))return (T) str;
		try {
			if(clazz.getSuperclass()!=null)
			if(clazz.getSuperclass().equals(Number.class)||clazz.equals(Boolean.class)) {
				return (T) clazz.getMethod("valueOf",String.class).invoke(null,str);
			}
		} catch (Exception e) {
			if(e instanceof InvocationTargetException)
			{
				Throwable e2 = e.getCause();
				if(e2 instanceof NumberFormatException)
					throw new ConvertException(clazz);
				else e.printStackTrace();
			}else e.printStackTrace();
		}
		return null;
	}

	
	/**获取转换类型*/
	public Class<?> getType() {
		return type;
	}
	/**设置转换类型*/

	public void setType(Class<?> type) {
		this.type = type;
	}

	/**获取补全函数集,若本节点没有函数集,自动获取父节点之*/
	public Map<String, TabExec<Sender>> getTabExecMap() {
		return tabExecMap!=null?tabExecMap:
			parentPath!=null?parentPath.getTabExecMap():null;
	}

	/**
	 * 设定补全函数集Map&lt;String,TabExec&gt;,以供子节点通过名称获取对应的函数
	 * @return 
	 * */
	public CmdPath<Sender> setTabExecMap(Map<String, TabExec<Sender>> tabExecMap) {
		this.tabExecMap = tabExecMap;return this;
	}
	
	/**
	 * (常用)添加函数进补全函数集,以供子节点通过名称获取对应的函数
	 * @param name 函数名称
	 * @param exe 函数
	 * <pre>
	 * TabExec(exe)说明: 只需返回所有可以提示的参数,无须判断字符串开头
	 * TabExec结构:
	 * (Sender 执行者,String 当前要补全的节点名称(如player),ComArgs 参数集)->{
	 * 	执行代码...
	 * 	return 要提示的参数集合(List);
	 * }
	 *  </pre>
	 * @return 
	 * */
	public CmdPath<Sender> addTabExec(String name,TabExec<Sender> exe) {
		if(tabExecMap==null)tabExecMap = new HashMap<String, TabExec<Sender>>();
		tabExecMap.put(name,exe);
		return this;
	}

	/**获取校验函数集,若本节点没有函数集,自动获取父节点之*/
	public Map<String, CheckExec<Sender>> getCheckExecMap() {
		return checkExecMap!=null?checkExecMap:
			parentPath!=null?parentPath.getCheckExecMap():null;
	}
	
	/**
	 * 设定校验函数集Map&lt;String,VerifyExec&gt;,以供子节点通过名称获取对应的函数
	 * @return 
	 * */
	public CmdPath<Sender> setCheckExecMap(Map<String, CheckExec<Sender>> checkExecMap) {
		this.checkExecMap = checkExecMap;
		return this;
	}
	
	/**
	 * (常用)添加函数进校验函数集,以供子节点通过名称获取对应的函数
	 * @param name 函数名称
	 * @param exe 函数
	 * <pre>
	 * VerifyExec说明: 校验参数是否合法
	 * VerifyExec结构:
	 * (Sender 执行者,ComArgs 参数集)->{
	 * 	执行代码...
	 * 	return 是否校验成功;
	 * }
	 * </pre>
	 * @return 
	 * */
	public CmdPath<Sender> addCheckExec(String name,CheckExec<Sender> exe) {
		if(checkExecMap==null)checkExecMap = new HashMap<String, CheckExec<Sender>>();
		checkExecMap.put(name,exe);
		return this;
	}
	
	public boolean isValuePath() {
		return isValuePath;
	}

	public CmdPath<Sender> setValuePath(boolean isValuePath) {
		this.isValuePath = isValuePath;return this;
	}

	public boolean isListPath() {
		return isListPath;
	}

	public CmdPath<Sender> setListPath(boolean isListPath) {
		this.isListPath = isListPath;return this;
	}

	public boolean isOptionalPath() {
		return isOptionalPath;
	}

	public CmdPath<Sender> setOptionalPath(boolean isOptionalPath) {
		this.isOptionalPath = isOptionalPath;return this;
	}

	public String getDefaultValue() {
		return defaultValue;
	}

	public CmdPath<Sender> setDefaultValue(String defaultValue) {
		this.defaultValue = defaultValue;return this;
	}
	
	public void sortPaths() {
		subPaths = subPaths.entrySet().stream()
		        .sorted((en1,en2)->{
		        	int a = (int) en1.getValue().getAttrOrDef("order",0);
		        	int b = (int) en2.getValue().getAttrOrDef("order",1);
		        	return a-b;
		        })
		        .collect(Collectors.toMap(
		                Entry::getKey,
		                Entry::getValue,
		                (oldVal, newVal) -> oldVal,
		                LinkedHashMap::new));
	}

	public String getOriginName() {
		return originName;
	}
	public void setOriginName(String originName) {
		this.originName = originName;
	}
}
